package br3akr.DeadlyDinnerParty.Server.Engine;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashSet;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import br3akr.DeadlyDinnerParty.Common.UpdateClient;
import br3akr.DeadlyDinnerParty.Server.Actions.Action;
import br3akr.DeadlyDinnerParty.Server.Actions.ActionResponse;
import br3akr.DeadlyDinnerParty.Server.Actions.CreateCharacterAction;
import br3akr.DeadlyDinnerParty.Server.Actions.LoginAction;
import br3akr.DeadlyDinnerParty.Server.Exceptions.DeadlyDinnerPartyException;
import br3akr.DeadlyDinnerParty.Server.World.Body;
import br3akr.DeadlyDinnerParty.Server.World.Exit;
import br3akr.DeadlyDinnerParty.Server.World.GameCharacter;
import br3akr.DeadlyDinnerParty.Server.World.Item;
import br3akr.DeadlyDinnerParty.Server.World.World;

public class Session implements Observer {
    private GameCharacter character;
    private String playerName;
    private boolean running;
    private ObjectOutputStream out;
    private LoginAction loginAction;
    private CreateCharacterAction createCharacterAction;

    public Session(ObjectOutputStream out) {
		running = true;
		this.out = out;
    }
    
    public void handleInput(String input) {
    	Action action = null;
    	try {
	    	if (!loginAction.isComplete()) {
	    		action = loginAction;
	    	}
	    	if (action == null && !createCharacterAction.isComplete()) {
	    		action = createCharacterAction;
	    	}
	    	if (action == null) {
		    	if (Server.world.haveEnoughPlayers() && character != null) {
					if (input != null && input.trim().length() != 0) {
						action = character.takeAction(input);
					}
		    	} else {
					updateClient(buildClientUpdate("There are not yet enough players. Please wait until more have connected.\nCurrent players: " + Server.world.getNumberOfPlayers() + ", Minimum required: " + World.REQUIRED_NUMBER_OF_PLAYERS));
		    	}
	    	}
	    	
			if (action != null) {
				if (action.isFree() || character == null || !character.isRecovering()) {
					try {
						ActionResponse actionResponse = action.doAction(input, this);
					
						if (actionResponse != null) {
							if (actionResponse.getAffectedAreas().size() == 0) {
								updateClient(buildClientUpdate(actionResponse.getActingCharacterDescription()));
							} else {
								Server.world.broadcastAction(actionResponse);
							}
						}
					} catch (DeadlyDinnerPartyException e) {
						updateClient(buildClientUpdate(e.getMessage()));
					}
				} else if (character.isRecovering()) {
					updateClient(buildClientUpdate("You aren't able to do that while your character is recovering."));
				}
			} else {
				updateClient(buildClientUpdate("You aren't able to do that."));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
    public void login() {
    	try {
    		LoginAction loginAction = new LoginAction();
    		this.loginAction = loginAction;
			ActionResponse loginActionResponse = loginAction.doAction(null, this);
			updateClient(buildClientUpdate(loginActionResponse.getActingCharacterDescription()));
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
    public void createCharacter() {
    	try {
    		CreateCharacterAction createCharacterAction = new CreateCharacterAction();
    		this.createCharacterAction = createCharacterAction;
    		try {
    			ActionResponse createCharacterActionResponse = createCharacterAction.doAction(null, this);
				updateClient(buildClientUpdate(createCharacterActionResponse.getActingCharacterDescription()));
    		} catch (DeadlyDinnerPartyException e) {
    			updateClient(buildClientUpdate(e.getMessage()));
    		}
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
    public ActionResponse handleLoginResponse(String loginResponse) {
		return loginAction.doAction(loginResponse, this);
    }
    
    public void updateClient(UpdateClient updateClient) throws IOException {
    	out.writeObject(updateClient);
    }
    
	public void update(Observable o, Object arg) {
		if (arg instanceof ActionResponse) {
			ActionResponse actionResponse = (ActionResponse)arg;
			Set<GameCharacter> targets = actionResponse.getTargetCharacters();
			Set<GameCharacter> onlookers = actionResponse.getOnlookers();
			try {
				if (actionResponse.getSystemMessage() != null) {
					updateClient(buildClientUpdate(actionResponse.getSystemMessage()));
				} else {
					if (actionResponse.getActingCharacter() == character) {
						updateClient(buildClientUpdate(actionResponse.getActingCharacterDescription()));
					} else if (targets.contains(character)) {
						updateClient(buildClientUpdate(actionResponse.getTargetCharacterDescription(character)));
					} else if (onlookers.contains(character)) {
						updateClient(buildClientUpdate(actionResponse.getOnlookerDescription(character)));
					}
				}
			} catch (Exception e) {
				System.out.println("Unable to update observers of an event.");
				e.printStackTrace();
			}
		} else {
			System.out.println("Error: Got an unhandled update.");
		}
	}
    
	public UpdateClient buildClientUpdate(String output) {
		Set<String> injuries = new HashSet<String>();
		Set<String> charactersInArea = new HashSet<String>();
		Set<String> bodiesInArea = new HashSet<String>();
		Set<String> itemsInArea = new HashSet<String>();
		Set<String> inventory = new HashSet<String>();
		Set<String> exits = new HashSet<String>();
		String leftHandItemName = null;
		String heldItemName = null;

		if (character != null) {
			if (character.getLocation() != null) {
				for (GameCharacter curChar : character.getLocation().getCharacters()) {
					if (!curChar.isHiddenFrom(character)) {
						charactersInArea.add(curChar.getName());
					}
				}
				
				for (Exit exit : character.getLocation().getExits()) {
					exits.add(exit.getExitDirection(character.getLocation()).toString());
				}
			}
			for (Item curItem : character.getInventory()) {
				inventory.add(curItem.getName());
			}
			if (character.getHeldItem() != null) {
				heldItemName = character.getHeldItem().getName();
			}
			for (Item curItem : character.getLocation().getItems()) {
				if (!curItem.isHiddenFrom(character)) {
					itemsInArea.add(curItem.getName());
				}
			}
			
			for (Body curBody : character.getLocation().getBodies()) {
				if (!curBody.isHiddenFrom(character)) {
					bodiesInArea.add(curBody.getCharacter().getName());
				}
			}
			return new UpdateClient(output, character.getName(), injuries,
					charactersInArea, leftHandItemName, 
					heldItemName, exits, inventory, itemsInArea, bodiesInArea,
					character.isRecovering());
		}
		return new UpdateClient(output, null, injuries,	charactersInArea, null, null, exits, null, null, null, false);
	}
    
    public boolean isRunning() {
        return running;
    }
    
    public void setRunning(boolean running) {
        this.running = running;
    }
    
    public GameCharacter getCharacter() {
        return character;
    }
    
    public void setPlayerName(String playerName) {
    	this.playerName = playerName;
    }
    
    public String getPlayerName() {
    	return playerName;
    }
    
    public void setCharacter(GameCharacter character) {
    	this.character = character;
    }
}